<template>
  <div class="tutorial-page">
    <div class="tutorial-header">
      <h1>Vue 混入 (Mixins)</h1>
      <p>了解混入(Mixin)的使用方法和注意事项</p>
    </div>

    <div class="tutorial-content">
      <div class="section">
        <h2>混入基础</h2>
        <p>混入 (mixin) 提供了一种非常灵活的方式，来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时，所有混入对象的选项将被"混合"进入该组件本身的选项。</p>
        
        <h3>基础示例</h3>
        <pre><code class="language-javascript">// 定义一个混入对象
const myMixin = {
  data() {
    return {
      message: 'hello',
      foo: 'abc'
    }
  },
  created() {
    console.log('mixin 对象的钩子被调用')
  },
  methods: {
    greet() {
      console.log('good morning!')
    }
  }
}

// 定义一个使用混入对象的应用
const app = Vue.createApp({
  mixins: [myMixin],
  data() {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created() {
    console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})</code></pre>
      </div>

      <div class="section">
        <h2>选项合并</h2>
        <p>当组件和混入对象含有同名选项时，这些选项会以恰当的方式进行"合并"。</p>
        
        <h3>数据对象合并</h3>
        <pre><code class="language-javascript">const mixin = {
  data() {
    return {
      message: 'hello',
      foo: 'abc',
      bar: 'mixin'
    }
  }
}

const app = Vue.createApp({
  mixins: [mixin],
  data() {
    return {
      message: 'goodbye',
      bar: 'app'
    }
  },
  created() {
    console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "app" }
  }
})</code></pre>
        <p>同名数据 property，组件数据优先。</p>
        
        <h3>钩子函数合并</h3>
        <pre><code class="language-javascript">const mixin = {
  created() {
    console.log('混入对象的钩子被调用')
  }
}

const app = Vue.createApp({
  mixins: [mixin],
  created() {
    console.log('组件钩子被调用')
  }
})

// => "混入对象的钩子被调用"
// => "组件钩子被调用"</code></pre>
        <p>同名钩子函数将合并为一个数组，因此都将被调用。另外，混入对象的钩子将在组件自身钩子之前调用。</p>
        
        <h3>值为对象的选项合并</h3>
        <pre><code class="language-javascript">const mixin = {
  methods: {
    foo() {
      console.log('foo from mixin')
    },
    conflicting() {
      console.log('from mixin')
    }
  }
}

const app = Vue.createApp({
  mixins: [mixin],
  methods: {
    bar() {
      console.log('bar from app')
    },
    conflicting() {
      console.log('from app')
    }
  }
})

const vm = app.mount('#app')

vm.foo() // => "foo from mixin"
vm.bar() // => "bar from app"
vm.conflicting() // => "from app"</code></pre>
        <p>值为对象的选项，如 methods、components 和 directives，将被合并为同一个对象。两个对象键名冲突时，取组件对象的键值对。</p>
      </div>

      <div class="section">
        <h2>全局混入</h2>
        <p>一旦使用全局混入，它将影响每一个之后创建的 Vue 实例。使用恰当时，这可以用来为自定义选项注入处理逻辑。</p>
        
        <pre><code class="language-javascript">// 为自定义的选项 'myOption' 注入一个处理器
app.mixin({
  created() {
    const myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

app.mount('#app')

// => "hello!"</code></pre>
        
        <div class="warning">
          <strong>谨慎使用全局混入！</strong>一旦使用全局混入，它将影响每一个之后创建的 Vue 实例。使用恰当时，这可以用来为自定义选项注入处理逻辑。不推荐在应用代码中使用全局混入。最好将其用于插件作者提供的插件功能。
        </div>
      </div>

      <div class="section">
        <h2>自定义选项合并策略</h2>
        <p>自定义选项在合并时，默认使用覆盖策略，即简单地替换已有值。如果想让自定义选项以自定义逻辑进行合并，可以在 app.config.optionMergeStrategies 中添加一个函数：</p>
        
        <pre><code class="language-javascript">const app = Vue.createApp({})

app.config.optionMergeStrategies.customOption = (toVal, fromVal) => {
  // return mergedVal
}</code></pre>
        
        <p>合并策略接收在父实例和子实例上定义的该选项的值，分别作为第一个和第二个参数。</p>
      </div>

      <div class="section">
        <h2>混入的实际应用</h2>
        
        <h3>表单验证混入</h3>
        <pre><code class="language-javascript">// formValidationMixin.js
export const formValidationMixin = {
  data() {
    return {
      errors: {}
    }
  },
  methods: {
    validateEmail(email) {
      const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
      return re.test(email)
    },
    validateRequired(value) {
      return value && value.trim().length > 0
    },
    clearErrors() {
      this.errors = {}
    },
    hasErrors() {
      return Object.keys(this.errors).length > 0
    }
  }
}</code></pre>
        <pre><code class="language-javascript">// LoginForm.vue
import { formValidationMixin } from './formValidationMixin.js'

export default {
  mixins: [formValidationMixin],
  data() {
    return {
      email: '',
      password: ''
    }
  },
  methods: {
    validateForm() {
      this.clearErrors()
      
      if (!this.validateRequired(this.email)) {
        this.errors.email = '邮箱不能为空'
      } else if (!this.validateEmail(this.email)) {
        this.errors.email = '请输入有效的邮箱地址'
      }
      
      if (!this.validateRequired(this.password)) {
        this.errors.password = '密码不能为空'
      }
      
      return !this.hasErrors()
    },
    submitForm() {
      if (this.validateForm()) {
        // 提交表单
        console.log('表单验证通过')
      }
    }
  }
}</code></pre>
        
        <h3>API 请求混入</h3>
        <pre><code class="language-javascript">// apiMixin.js
export const apiMixin = {
  data() {
    return {
      loading: false,
      error: null
    }
  },
  methods: {
    async apiCall(promise) {
      this.loading = true
      this.error = null
      
      try {
        const result = await promise
        return result
      } catch (err) {
        this.error = err.message || '请求失败'
        throw err
      } finally {
        this.loading = false
      }
    }
  }
}</code></pre>
        <pre><code class="language-javascript">// UserList.vue
import { apiMixin } from './apiMixin.js'

export default {
  mixins: [apiMixin],
  data() {
    return {
      users: []
    }
  },
  async created() {
    await this.fetchUsers()
  },
  methods: {
    async fetchUsers() {
      try {
        const response = await this.apiCall(fetch('/api/users'))
        this.users = await response.json()
      } catch (error) {
        console.error('获取用户列表失败:', error)
      }
    }
  }
}</code></pre>
      </div>

      <div class="section">
        <h2>混入的局限性和注意事项</h2>
        
        <h3>命名冲突</h3>
        <ul>
          <li>混入中的属性和方法可能与组件中的同名属性和方法冲突</li>
          <li>混入之间也可能存在命名冲突</li>
        </ul>
        
        <h3>可读性问题</h3>
        <ul>
          <li>过度使用混入可能导致组件逻辑不清晰</li>
          <li>难以追踪属性和方法的来源</li>
        </ul>
        
        <h3>Composition API 替代方案</h3>
        <p>Vue 3 推荐使用 Composition API 来替代混入，因为它提供了更好的逻辑复用和组织方式：</p>
        <pre><code class="language-javascript">// 使用 Composition API 的组合函数
// useFormValidation.js
import { ref } from 'vue'

export function useFormValidation() {
  const errors = ref({})
  
  const validateEmail = (email) => {
    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    return re.test(email)
  }
  
  const validateRequired = (value) => {
    return value && value.trim().length > 0
  }
  
  const clearErrors = () => {
    errors.value = {}
  }
  
  const hasErrors = () => {
    return Object.keys(errors.value).length > 0
  }
  
  return {
    errors,
    validateEmail,
    validateRequired,
    clearErrors,
    hasErrors
  }
}</code></pre>
      </div>
    </div>
  </div>
</template>

<script setup>
// 页面逻辑可以在这里添加
</script>

<style scoped>
@import '@/assets/tutorial-styles.css';

.warning {
  background-color: #fff3cd;
  border: 1px solid #ffeaa7;
  color: #856404;
  padding: 15px;
  border-radius: 5px;
  margin: 20px 0;
}

.warning strong {
  color: #856404;
}
</style>